package be.destin.adp;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/*
 * One Adapt rule (within a transformation) e.g. pattern=replacement.
 */

public class AdpRule extends Adp {
	AdpTransformation transformation;
	String expression; // Computed RegExp
	/*
	g 	Substitute for all occurrences of the regular expression. not just the first one.
	i 	Do case-insensitive pattern matching. This modifier corresponds to the java.util.regex.Pattern.CASE_INSENSITIVE flag.
	m 	Treat the string is consisting of multiple lines. This modifier corresponds to the java.util.regex.Pattern.MULTILINE flag. It changes the meaning of "^" and "$" so that they match just after or just before, respectively, a line terminator or the end of the input sequence. By default these expressions only match at the beginning and the end of the entire input sequence.
	o 	Compile once. This modifier is ignored, since regular expressions are always compiled once and stored in the internal LRU buffer.
	u 	Enables Unicode-aware case folding. This modifier corresponds to the java.util.regex.UNICODE_CASE flag. When this modifier is specified, case-insensitive matching, when enabled by the CASE_INSENSITIVE flag, is done in a manner consistent with the Unicode Standard. By default, case-insensitive matching assumes that only characters in the US-ASCII charset are being matched. Specifying this flag may impose a performance penalty.
	x 	Permits whitespace and comments in a pattern. This modifier corresponds to the java.util.regex.Pattern.COMMENTS flag. When this mode is active, whitespace is ignored, and embedded comments starting with # are ignored until the end of a line. 
	 */
	Pattern pattern; // Compiled regexp
	//TODO String[] varNames; // Variable names if not JAVA 7 (TODO if we want to support JAVA 6)
	AdpWINdata win = AdpWINdata.WIN_continue;
	String replacement = "";
	
	private static final int NO_MATCH = -1;
	
	public AdpRule (AdpTransformation transformation) {
		this.transformation = transformation;
	}

	public AdpRule (AdpTransformation transformation, Rex expression, String replacement, AdpWINdata win) {
		transformation.addRule(this); // sets this.transformation...
		this.expression = expression.getExpression();
		if (replacement != null) this.setReplacement(replacement);
		if (win != null) this.setWin(win);
	}

	/**
	 * Converts a wildcard rule to a regex rule.
	 *
	 * @param wildcardRule the wildcard rule.
	 * @param exactMatch   <code>true</code> results in an regex that will match the entire string, while
	 *                     <code>false</code> will match the start of the string.
	 * @return The regex rule.
	 */
	public static String convertWildcardsToRegex( String wildcardRule, boolean exactMatch )
	{
		StringBuffer regex = new StringBuffer();
		int index = 0;
		final int len = wildcardRule.length();
		while ( index < len )
		{
			final int nextQ = wildcardRule.indexOf( '?', index );
			final int nextS = wildcardRule.indexOf( '*', index );
			if ( nextQ == NO_MATCH && nextS == NO_MATCH )
			{
				regex.append( quote( wildcardRule.substring( index ) ) );
				break;
			}
			int nextIndex;
			if ( nextQ == NO_MATCH )
			{
				nextIndex = nextS;
			}
			else if ( nextS == NO_MATCH )
			{
				nextIndex = nextQ;
			}
			else
			{
				nextIndex = Math.min( nextQ, nextS );
			}
			if ( index < nextIndex )
			{
				regex.append( quote( wildcardRule.substring( index, nextIndex ) ) );
			}
			char c = wildcardRule.charAt( nextIndex );
			if ( c == '?' )
			{
				regex.append( c );
			}
			else
			{
				regex.append( c+'*' );
			}
			index = nextIndex + 1;
		}
		if ( !exactMatch )
		{
			regex.append( ".*" );
		}
		return regex.toString();
	}

	/**
	 * @return the transformation
	 */
	public AdpTransformation getTransformation() {
		return transformation;
	}
	/**
	 * @param transformation the transformation to set
	 */
	public void setTransformation(AdpTransformation transformation) {
		this.transformation = transformation;
	}
	/**
	 * @return the expression
	 */
	public String getExpression() {
		return expression;
	}
	/**
	 * @param expression the expression to set
	 */
	public void setExpression(String expression) {
		this.expression = expression;
		pattern = null;
	}
	/**
	 * @return the pattern
	 */
	public Pattern getPattern() {
		if (this.pattern == null) {
			this.pattern = Pattern.compile(this.expression,Pattern.CASE_INSENSITIVE+Pattern.UNICODE_CASE);
		}
		return this.pattern;
	}
		/*
		 * Parameters:
regex The expression to be compiled
flags Match flags, a bit mask that may include CASE_INSENSITIVE, MULTILINE, DOTALL, UNICODE_CASE, CANON_EQ, UNIX_LINES, LITERAL and COMMENTS
Throws:
IllegalArgumentException - If bit values other than those corresponding to the defined match flags are set in flags
PatternSyntaxException - If the expression's syntax is invalid
		 */
	/**
	 * @return the varNames
	 */
	public String[] getVarNames() {
		return new String[0];
	}

	/**
	 * @return the replacement
	 */
	public String getReplacement() {
		return replacement;
	}

	/**
	 * @param replacement the replacement to set
	 */
	public void setReplacement(String replacement) {
		this.replacement = replacement;
	}

	public static String quoteReplacement(final String str) {
		return Matcher.quoteReplacement(str);
	/**
	 * Replacement for Matcher.quoteReplacement from JDK 1.5
		if ((str.indexOf('$') < 0) && (str.indexOf('\\') < 0)) {
			return str;
		}
		return str.replaceAll("[$\\\\]", "\\\\$0");
	 */
	}

	/**
	 * @return the win
	 */
	public AdpWINdata getWin() {
		return win;
	}
	/**
	 * @param win the win to set
	 */
	public void setWin(AdpWINdata win) {
		this.win = win;
	}

	/*
	 * Usually overridden to implement specific treatment in Java when pattern is matched...
	 */
	public String found (AdpTransformationExec transfoExec, Matcher matcher) {
		StringBuffer sb = new StringBuffer();
		matcher.appendReplacement(sb, replacement);
		return sb.toString();
	}

	/*
	 * Utility functions outside of normal ADAPT style pattern matching
	 */
	
	/*
	 * Count the pattern in a string
	 */
	public int count(String input) {
		if (input == null) return 0;
		if (pattern == null) return NO_MATCH;
		Matcher matcher = pattern.matcher(input);
		int i = 0;
		while (matcher.find()) i++;
		return i;
	}

	public String[] lastCaptures(String input) {
		if (input == null) return null;
		if (pattern == null) return null;
		Matcher matcher = pattern.matcher(input);
		String[] results = new String[matcher.groupCount() + 1];

		while (matcher.find()) {
			for(int i = 0 ; i < results.length ; i++) {
				results[i] = matcher.group(i);
			}
		}
		return results;
	}

	/**
	 * Extract all matches from the given String and returns them.
	 *
	 * @param text The String to search
	 * @return A List of Strings matching the pattern, or an
	 * empty List if none.
	 */
	public List<String> uniqueMatches(String text) {
		if (pattern == null) return null;
		List<String> matches = new ArrayList<String>();
		if (text != null) {
			Matcher match = pattern.matcher(text);

			while (match.find()) {
				String str = match.group(0);
				if (str != null && !str.isEmpty() && !matches.contains(str))
					matches.add(str);
			}
		}
		return matches;
	}

	/*
	 * Replace the pattern in a string by some replacement
	 * all is true if all occurrences are to be replaced (false if only the first)
	 * input is input text to process
	 * rep is a constant replacement text
	 * (pattern must have been set previously)
	 */
	public String replace(boolean all, String input, String rep) {
		if (input == null) return null;
		if (pattern == null) return input;
		if (rep == null) rep = "";
		rep = Matcher.quoteReplacement(rep); 
		Matcher matcher = pattern.matcher(input);
		StringBuffer sb = new StringBuffer();
		while (matcher.find()) {
			matcher.appendReplacement(sb, rep);
			if (!all) break;
		}
		matcher.appendTail(sb);
		return sb.toString();
	}

	/*
	 * Replace the pattern in a string by some replacement
	 * all is true if all occurrences are to be replaced (false if only the first)
	 * input is input text to process
	 * rep is a replacement text where $0 .. $9 will be replaced by groups captured by the pattern
	 * (pattern must have been set previously)
	 */
	public String replaceCapt(boolean all, String input, String rep) {
		if (input == null) return null;
		if (pattern == null) return input;
		Matcher matcher = pattern.matcher(input);
		StringBuffer sb = new StringBuffer();
		while (matcher.find()) {
			matcher.appendReplacement(sb, rep);
			if (!all) break;
		}
		matcher.appendTail(sb);
		return sb.toString();
	}

	/** Testing method to see if some Strings match a pattern. */
	public void test(String... rules) {
		if (pattern == null) System.out.println(expression+" is not compiled");
		else {
			System.out.println(pattern);
			for (String rule : rules) {
				Matcher matcher = pattern.matcher(rule.trim());
				System.out.println(rule.trim() + ":" + matcher.matches());
				if (matcher.matches()) {
					System.out.print("groups:" + matcher.groupCount());
					for (int j = 0; j < matcher.groupCount(); j++) {
						System.out.print("\t|" + matcher.group(j + 1) + "|");
					}
					System.out.println();
				}
			}
		}
	}

}
